function Client(server_url, key, magic, onlogout_callback)
{
    this.username = undefined;
    this.auth = null;
    this.key = key;
    this.magic = magic;
    this.queue = []
    this.req = new XMLHttpRequest();

    this.process = function()
    {
        if(this.req.readyState != 0) return;
        if(this.queue.length == 0) return;
        
        var job = this.queue.pop();
        var obj = job[0], onreply = job[1], onerror = job[2];
        var req = this.req;
    
        req.open('POST', server_url, true);
        req.setRequestHeader('Content-Type', 'application/json');
        
        var client = this;
        req.onreadystatechange = function() {
            if(req.readyState == 4)
            {
                if(req.status != 200)
                {
                    if(req.status != 0)
                        onerror('HTTP Error ' + req.status + ' ' + req.statusText
                            + ':' + req.responseText);
                    return;
                }
            
                if(req.getResponseHeader('Content-Type') != 'application/json')
                {
                    client.req = new XMLHttpRequest();
                    onerror("Invalid content type in reply");
                } else
                {
                    reply = JSON.parse(req.responseText);
                    client.req = new XMLHttpRequest();
                    
                    if(reply.response == 'loggedout')
                    {
                        client.onlogoutWrapper();
                        onerror("Logged out");
                    }
                    else onreply(reply);
                }

                client.process();
            }
        };
        
        req.onerror = function(evt) {
            client.req = new XMLHttpRequest();
            onerror('' + evt);
            client.process();
        }
    
        req.send(JSON.stringify(obj));
        return req;
    }
    
    this.request = function(obj, onreply, onerror) {
        this.queue = [[obj, onreply, onerror]].concat(this.queue);
        this.process();
    }
    
    this.wrapCommand = function(command, params)
    {
        wrapped = { 'command': command, 'params': params };
        wrapped[key] = magic;
        if(this.username && this.auth)
        {
            wrapped['username'] = this.username;
            wrapped['auth'] = this.auth;
        }
        return wrapped;
    }
    
    this.command = function(command, params, onreply, onerror)
    {
        return this.request(this.wrapCommand(command, params), onreply, onerror);
    }
    
    var client = this;
    
    this.login = function(username, password, onlogin, onerror)
    {
        this.command(
            'login',
            {'username': username, 'password': password},
            function(reply) {
                if(reply.response != 'login') onerror('Login failed: ' + reply.msg);
                else
                {
                    client.username = username; client.auth = reply.auth;
                    onlogin(username); 
                }
            },
            onerror
        );            
    }
    
    this.logout = function(onerror)
    {
        var client = this;
        this.command(
            'logout',
            {},
            function(reply) {
                if(reply.response != 'logout') onerror('Logout failed: ' + reply.msg);
                else
                {
                    client.onlogoutWrapper();
                }
            },
            onerror
        );
    }
    
    this.onlogoutWrapper = function()
    {
        this.username = undefined;
        this.auth = null;
        return onlogout_callback();
    }
}

